{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a8b892c8",
   "metadata": {},
   "source": [
    "Printed and electronic copies of *Modeling and Simulation in Python* are available from [No Starch Press](https://nostarch.com/modeling-and-simulation-python) and [Bookshop.org](https://bookshop.org/p/books/modeling-and-simulation-in-python-allen-b-downey/17836697?ean=9781718502161) and [Amazon](https://amzn.to/3y9UxNb)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "signal-format",
   "metadata": {},
   "source": [
    "# Iteration"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "imported-table",
   "metadata": {
    "tags": []
   },
   "source": [
    "*Modeling and Simulation in Python*\n",
    "\n",
    "Copyright 2021 Allen Downey\n",
    "\n",
    "License: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "electoral-turkey",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# install Pint if necessary\n",
    "\n",
    "try:\n",
    "    import pint\n",
    "except ImportError:\n",
    "    !pip install pint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "formal-context",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# download modsim.py if necessary\n",
    "\n",
    "from os.path import basename, exists\n",
    "\n",
    "def download(url):\n",
    "    filename = basename(url)\n",
    "    if not exists(filename):\n",
    "        from urllib.request import urlretrieve\n",
    "        local, _ = urlretrieve(url, filename)\n",
    "        print('Downloaded ' + local)\n",
    "    \n",
    "download('https://raw.githubusercontent.com/AllenDowney/' +\n",
    "         'ModSimPy/master/modsim.py')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "progressive-typing",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# import functions from modsim\n",
    "\n",
    "from modsim import *"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "coral-steering",
   "metadata": {},
   "source": [
    "To paraphrase two Georges, \"All models are wrong, but some models are\n",
    "more wrong than others.\" This chapter demonstrates the process we\n",
    "use to make models less wrong.\n",
    "\n",
    "As an example, we'll review the bike share model from the previous\n",
    "chapter, consider its strengths and weaknesses, and gradually improve\n",
    "it. We'll also see ways to use the model to understand the behavior of\n",
    "the system and evaluate designs intended to make it work better."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "plastic-trigger",
   "metadata": {},
   "source": [
    "This chapter is available as a Jupyter notebook where you can read the text, run the code, and work on the exercises. \n",
    "Click here to access the notebooks: <https://allendowney.github.io/ModSimPy/>."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "twelve-length",
   "metadata": {},
   "source": [
    "## Iterative Modeling\n",
    "\n",
    "The model we have so far is simple, but it is based on unrealistic\n",
    "assumptions. Before you go on, take a minute to review the model from\n",
    "the previous chapter. What assumptions is it based on? Make a list of\n",
    "ways this model might be unrealistic; that is, what are the differences between the model and the real world?\n",
    "\n",
    "Here are some of the differences on my list:\n",
    "\n",
    "-   In the model, a student is equally likely to arrive during any\n",
    "    one-minute period. In reality, this probability varies depending on time of day, day of the week, etc.\n",
    "\n",
    "-   The model does not account for travel time from one bike station to another.\n",
    "\n",
    "-   The model does not check whether a bike is available, so it's\n",
    "    possible for the number of bikes to be negative (as you might have\n",
    "    noticed in some of your simulations)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "sweet-heater",
   "metadata": {},
   "source": [
    "Some of these modeling decisions are better than others. For example,\n",
    "the first assumption might be reasonable if we simulate the system for a short period of time, like one hour.\n",
    "\n",
    "The second assumption is not very realistic, but it might not affect the results very much, depending on what we use the model for.\n",
    "On the other hand, the third assumption seems more problematic.\n",
    "It is relatively easy to fix, though; in this chapter, we'll fix it.\n",
    "\n",
    "This process, starting with a simple model, identifying the most\n",
    "important problems, and making gradual improvements, is called\n",
    "*iterative modeling*.\n",
    "\n",
    "For any physical system, there are many possible models, based on\n",
    "different assumptions and simplifications. It often takes several\n",
    "iterations to develop a model that is good enough for the intended\n",
    "purpose, but no more complicated than necessary."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "female-salem",
   "metadata": {},
   "source": [
    "## More Than One State Object\n",
    "\n",
    "Before we go on, I want to make a few changes to the code from the\n",
    "previous chapter. First I'll generalize the functions we wrote so they\n",
    "take a `State` object as a parameter. Then, I'll make the code more\n",
    "readable by adding documentation.\n",
    "\n",
    "Here is one of the functions from the previous chapter, `bike_to_wellesley`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "embedded-heavy",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bike_to_wellesley():\n",
    "    bikeshare.olin -= 1\n",
    "    bikeshare.wellesley += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "artificial-remains",
   "metadata": {},
   "source": [
    "When this function is called, it modifies `bikeshare`. As long as there\n",
    "is only one `State` object, that's fine, but what if there is more than\n",
    "one bike share system in the world? Or what if we want to run more than\n",
    "one simulation?\n",
    "\n",
    "This function would be more flexible if it took a `State` object as a\n",
    "parameter. Here's what that looks like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "unusual-advancement",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bike_to_wellesley(state):\n",
    "    state.olin -= 1\n",
    "    state.wellesley += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "serial-mortality",
   "metadata": {},
   "source": [
    "The name of the parameter is `state`, rather than `bikeshare`, as a\n",
    "reminder that the value of `state` could be any `State` object, not just the one we called `bikeshare`.\n",
    "\n",
    "This version of `bike_to_wellesley` requires a `State` object as a\n",
    "parameter, so we have to provide one when we call it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "packed-hungarian",
   "metadata": {},
   "outputs": [],
   "source": [
    "bikeshare = State(olin=10, wellesley=2)\n",
    "bike_to_wellesley(bikeshare)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fewer-rhythm",
   "metadata": {},
   "source": [
    "Again, the argument we provide gets assigned to the parameter, so this\n",
    "function call has the same effect as:\n",
    "\n",
    "```\n",
    "state = bikeshare \n",
    "state.olin -= 1 \n",
    "state.wellesley += 1\n",
    "```\n",
    "\n",
    "Now we can create as many `State` objects as we want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "right-assessment",
   "metadata": {},
   "outputs": [],
   "source": [
    "bikeshare1 = State(olin=10, wellesley=2)\n",
    "bikeshare2 = State(olin=2, wellesley=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "occupied-navigator",
   "metadata": {},
   "source": [
    "And update them independently:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "cleared-advocacy",
   "metadata": {},
   "outputs": [],
   "source": [
    "bike_to_wellesley(bikeshare1)\n",
    "bike_to_wellesley(bikeshare2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "brown-ferry",
   "metadata": {},
   "source": [
    "Changes in `bikeshare1` do not affect `bikeshare2`, and vice versa. So\n",
    "we can simulate different bike share systems, or run multiple\n",
    "simulations of the same system."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "magnetic-packing",
   "metadata": {},
   "source": [
    "## Documentation\n",
    "\n",
    "Another problem with the code we have so far is that it contains no\n",
    "*documentation*.\n",
    "Documentation is text we add to a program to help\n",
    "other programmers read and understand it. It has no effect on the\n",
    "program when it runs.\n",
    "\n",
    "There are two kinds of documentation, *docstrings* and *comments*:\n",
    "\n",
    "* A docstring is a string in triple quotes that appears at the beginning of a function.\n",
    "\n",
    "* A comment is a line of text that begins with a hash symbol, `#`.\n",
    "\n",
    "Here's a version of `bike_to_olin` with a docstring and a comment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "moral-parallel",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bike_to_olin(state):\n",
    "    \"\"\"Move one bike from Wellesley to Olin.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    \"\"\"\n",
    "    # We decrease one state variable and increase the\n",
    "    # other so the total number of bikes is unchanged.\n",
    "    state.wellesley -= 1\n",
    "    state.olin += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "therapeutic-utility",
   "metadata": {},
   "source": [
    "Docstrings follow a conventional format:\n",
    "\n",
    "-   The first line is a single sentence that describes what the function does.\n",
    "\n",
    "-   The following lines explain what the parameters are.\n",
    "\n",
    "A function's docstring should include the information someone needs to\n",
    "know to *use* the function; it should not include details about how the function works.\n",
    "\n",
    "Comments provide details about how the function works, especially if there is something that would not be obvious to someone reading the program."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "american-clear",
   "metadata": {},
   "source": [
    "## Negative Bikes\n",
    "\n",
    "The changes we've made so far improve the quality of the code, but we\n",
    "haven't done anything to improve the quality of the model. Let's do that now.\n",
    "\n",
    "Currently the simulation does not check whether a bike is available when a customer arrives, so the number of bikes at a location can be\n",
    "negative. That's not very realistic.\n",
    "\n",
    "Here's a version of `bike_to_olin` that fixes the problem:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "divine-leisure",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bike_to_olin(state):\n",
    "    if state.wellesley == 0:\n",
    "        return\n",
    "    state.wellesley -= 1\n",
    "    state.olin += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "decimal-denver",
   "metadata": {},
   "source": [
    "The first line checks whether the number of bikes at Wellesley is zero. If so, it uses a *return statement*, which causes the function to end immediately, without running the rest of the statements. So if there are no bikes at Wellesley, we return from `bike_to_olin` without changing the state.\n",
    "\n",
    "We can test it by initializing the state with no bikes at Wellesley and calling `bike_to_olin`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "choice-cooking",
   "metadata": {},
   "outputs": [],
   "source": [
    "bikeshare = State(olin=12, wellesley=0)\n",
    "bike_to_olin(bikeshare)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "persistent-denmark",
   "metadata": {},
   "source": [
    "The state of the system should be unchanged."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "twelve-moderator",
   "metadata": {},
   "outputs": [],
   "source": [
    "show(bikeshare)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "criminal-general",
   "metadata": {},
   "source": [
    "No more negative bikes (at least at Wellesley)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "gorgeous-found",
   "metadata": {},
   "source": [
    "## Comparison Operators\n",
    "\n",
    "The updated version of `bike_to_olin` uses the equals operator, `==`, which compares two values and returns `True` if they are equal, and `False` otherwise.\n",
    "\n",
    "It is easy to confuse the equals operator with the assignment operator, `=`, which assigns a value to a variable. For example, the following statement creates a variable, `x`, if it doesn't already exist, and gives it the value `5`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "level-burns",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "weighted-monster",
   "metadata": {},
   "source": [
    "On the other hand, the following statement checks whether `x` is `5` and\n",
    "returns `True` or `False`. It does not create `x` or change its value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "civic-remains",
   "metadata": {},
   "outputs": [],
   "source": [
    "x == 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "forward-perth",
   "metadata": {},
   "source": [
    "You can use the equals operator in an `if` statement, like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "affecting-naples",
   "metadata": {},
   "outputs": [],
   "source": [
    "if x == 5:\n",
    "    print('yes, x is 5')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "consolidated-anatomy",
   "metadata": {},
   "source": [
    "If you make a mistake and use `=` in an `if` statement, like this:\n",
    "\n",
    "```\n",
    "if x = 5:\n",
    "    print('yes, x is 5')\n",
    "```\n",
    "\n",
    "That's a *syntax error*, which means that the structure of the program is invalid. Python will print an error message and the program won't run."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "twelve-defensive",
   "metadata": {},
   "source": [
    "The equals operator is one of Python's *comparison operators*; the complete list is in the following table.\n",
    "\n",
    "| Operation             | Symbol |\n",
    "|-----------------------|--------|\n",
    "| Less than             | `<`    |\n",
    "| Greater than          | `>`    |\n",
    "| Less than or equal    | `<=`   |\n",
    "| Greater than or equal | `>=`   |\n",
    "| Equal                 | `==`   |\n",
    "| Not equal             | `!=`   |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "center-sequence",
   "metadata": {},
   "source": [
    "## Metrics\n",
    "\n",
    "Getting back to the bike share system, at this point we have the ability to simulate the behavior of the system. Since the arrival of customers is random, the state of the system is different each time we run a\n",
    "simulation. Models like this are called random or *stochastic*; models\n",
    "that do the same thing every time they run are *deterministic*.\n",
    "\n",
    "Suppose we want to use our model to predict how well the bike share\n",
    "system will work, or to design a system that works better. First, we\n",
    "have to decide what we mean by \"how well\" and \"better\".\n",
    "\n",
    "From the customer's point of view, we might like to know the probability of finding an available bike. From the system-owner's point of view, we might want to minimize the number of customers who don't get a bike when they want one, or maximize the number of bikes in use. Statistics like these that quantify how well the system works are called *metrics*.\n",
    "\n",
    "As an example, let's measure the number of unhappy customers.\n",
    "Here's a version of `bike_to_olin` that keeps track of the number of\n",
    "customers who arrive at a station with no bikes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "arbitrary-ferry",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bike_to_olin(state):\n",
    "    if state.wellesley == 0:\n",
    "        state.wellesley_empty += 1\n",
    "        return\n",
    "    state.wellesley -= 1\n",
    "    state.olin += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "severe-contact",
   "metadata": {},
   "source": [
    "If a customer arrives at the Wellesley station and finds no bike\n",
    "available, `bike_to_olin` updates `wellesley_empty`, which counts the\n",
    "number of unhappy customers.\n",
    "\n",
    "This function only works if we initialize `wellesley_empty` when we\n",
    "create the `State` object, like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "cardiovascular-montgomery",
   "metadata": {},
   "outputs": [],
   "source": [
    "bikeshare = State(olin=12, wellesley=0, \n",
    "                  wellesley_empty=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "computational-prior",
   "metadata": {},
   "source": [
    "We can test it by calling `bike_to_olin`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "cosmetic-above",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "bike_to_olin(bikeshare)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "pleased-gasoline",
   "metadata": {},
   "source": [
    "After this update, there should be 12 bikes at Olin, no bikes at Wellesley, and one unhappy customer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "bulgarian-palestine",
   "metadata": {},
   "outputs": [],
   "source": [
    "show(bikeshare)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "revised-associate",
   "metadata": {},
   "source": [
    "Looks good!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "native-kidney",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this chapter, we wrote several versions of `bike_to_olin`:\n",
    "\n",
    "* We added a parameter, `state`, so we can work with more than one `State` object.\n",
    "\n",
    "* We added a docstring that explains how to use the function and a comment that explains how it works.\n",
    "\n",
    "* We used a conditional operator, `==`, to check whether a bike is available, in order to avoid negative bikes.\n",
    "\n",
    "* We added a state variable, `wellesley_empty`, to count the number of unhappy customers, which is a metric we'll use to quantify how well the system works.\n",
    "\n",
    "In the exercises, you'll update `bike_to_wellesley` the same way and test it by running a simulation."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "impaired-cyprus",
   "metadata": {},
   "source": [
    "## Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "careful-hacker",
   "metadata": {
    "tags": []
   },
   "source": [
    "Here's the code we have so far, with docstrings, all in one place."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "wrong-internet",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def run_simulation(state, p1, p2, num_steps):\n",
    "    \"\"\"Simulate the given number of time steps.\n",
    "    \n",
    "    state: State object\n",
    "    p1: probability of an Olin->Wellesley customer arrival\n",
    "    p2: probability of a Wellesley->Olin customer arrival\n",
    "    num_steps: number of time steps\n",
    "    \"\"\"\n",
    "    results = TimeSeries()\n",
    "    results[0] = state.olin\n",
    "    \n",
    "    for i in range(num_steps):\n",
    "        step(state, p1, p2)\n",
    "        results[i+1] = state.olin\n",
    "        \n",
    "    results.plot(label='Olin')\n",
    "    decorate(title='Olin-Wellesley Bikeshare',\n",
    "             xlabel='Time step (min)', \n",
    "             ylabel='Number of bikes')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "instrumental-copyright",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def step(state, p1, p2):\n",
    "    \"\"\"Simulate one time step.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    p1: probability of an Olin->Wellesley ride\n",
    "    p2: probability of a Wellesley->Olin ride\n",
    "    \"\"\"\n",
    "    if flip(p1):\n",
    "        bike_to_wellesley(state)\n",
    "    \n",
    "    if flip(p2):\n",
    "        bike_to_olin(state)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "improved-renaissance",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def bike_to_olin(state):\n",
    "    \"\"\"Move one bike from Wellesley to Olin.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    \"\"\"\n",
    "    if state.wellesley == 0:\n",
    "        state.wellesley_empty += 1\n",
    "        return\n",
    "    state.wellesley -= 1\n",
    "    state.olin += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "unavailable-maker",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def bike_to_wellesley(state):\n",
    "    \"\"\"Move one bike from Olin to Wellesley.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    \"\"\"\n",
    "    state.olin -= 1\n",
    "    state.wellesley += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bigger-rapid",
   "metadata": {},
   "source": [
    "### Exercise 1\n",
    "\n",
    " Modify `bike_to_wellesley` so it checks whether a bike is available at Olin.  If not, it should add `1` to `olin_empty`.\n",
    "\n",
    "To test it, create a `State` that initializes `olin` and `olin_empty` to `0`, run `bike_to_wellesley`, and check the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "phantom-carter",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "adopted-contrary",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "comparable-natural",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "attractive-amendment",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "possible-initial",
   "metadata": {},
   "source": [
    "### Exercise 2\n",
    "\n",
    " Now run the simulation with parameters `p1=0.3`, `p2=0.2`, and `num_steps=60`, and confirm that the number of bikes is never negative.\n",
    "\n",
    "Start with this initial state:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "eleven-constraint",
   "metadata": {},
   "outputs": [],
   "source": [
    "bikeshare = State(olin=10, wellesley=2,\n",
    "                  olin_empty=0, wellesley_empty=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "immune-shock",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
